<?php
// WebSVN - Subversion repository viewing via the web using PHP
// Copyright (C) 2004-2006 Tim Armes
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// --
//
// auth.php
//
// Handle reading and interpretation of an SVN auth file

require_once("include/accessfile.php");

define("UNDEFINED", 0);
define("ALLOW", 1);
define("DENY", 2);

class Authentication
{
   var $rights;
   var $user;
   var $usersGroups = array();
   
   // {{{ __construct

   function Authentication($accessfile)
   {
      $this->rights = new IniFile();
      $this->rights->readIniFile($accessfile);
      $this->setUsername();
      $this->identifyGroups();
   }

   // }}}
   
	// {{{ setUsername()
	//
	// Set the username from the current http session

	function setUsername()
	{
        if (isset($_SERVER["REMOTE_USER"]))
        {
           $this->user = $_SERVER["REMOTE_USER"];
        }
        elseif (isset($_SERVER["REDIRECT_REMOTE_USER"]))
        {
           $this->user = $_SERVER["REDIRECT_REMOTE_USER"];
        }
	}

   // }}}

   // {{{ identifyGroups()
   //
   // Checks to see which groups the user belongs to

   function identifyGroups()
   {
      $this->usersGroups[] = "*";

      if (is_array($this->rights->getValues("groups")))
      {
         foreach ($this->rights->getValues("groups") as $group => $names)
         {
            if (in_array(strtolower($this->user), preg_split('/\s*,\s*/', $names)))
              $this->usersGroups[] = "@" . $group;
              
            foreach ($this->usersGroups as $users_group)
            {
              if (in_array($users_group, preg_split('/\s*,\s*/', $names)))
                $this->usersGroups[] = "@" . $group;
            }
         }
      }
   }

   // }}}

   // {{{ inList
   //
   // Check if the user is in the given list and return their read status
   // if they are (UNDEFINED, ALLOW or DENY)
   
   function inList($accessors, $user)
   {
      $output = UNDEFINED;
      foreach($accessors As $key => $rights)
      {
         $keymatch = false;
  
         if (in_array($key, $this->usersGroups) || !strcmp($key, strtolower($user)))
            $keymatch = true;
      
         if ($keymatch)
         {
            if (strpos($rights, "r") !== false)
               return ALLOW;
             else
               $output = DENY;
         }
      }
      
      return $output;
   }

   // }}}
   
   // {{{ hasReadAccess
   //
   // Returns true if the user has read access to the given path
   
   function hasReadAccess($repos, $path, $checkSubFolders = false)
   {
      $access = UNDEFINED;
      $repos = strtolower($repos); // .ini parser converts groups to lower-case
      $path = strtolower($path);
      if ($path == '' || $path{0} != "/")
         $path = "/$path";
  
      // If were told to, we should check sub folders of the path to see if there's
      // a read access below this level.  This is used to display the folders needed
      // to get to the folder to which read access is granted.
      
      if ($checkSubFolders)
      {
         $sections = $this->rights->getSections();
         
         foreach($sections As $section => $accessers)
         {
            $qualified = $repos.":".$path;
            $len = strlen($qualified);
            if ($len < strlen($section) && strncmp($section, $qualified, $len) == 0)
            {
               $access = $this->inList($accessers, $this->user);
            }

            if ($access != ALLOW)
            {
               $len = strlen($path);
               if ($len < strlen($section) && strncmp($section, $path, $len) == 0)
               {
                  $access = $this->inList($accessers, $this->user);
               }
            }
            
            if ($access == ALLOW)
               break;
         }     
      }
      
      // If we still don't have access, check each subpath of the path until we find an
      // access level...
      
      if ($access != ALLOW)
      {
         $access = UNDEFINED;
         
         do
         {
            $accessers = $this->rights->getValues($repos.":".$path);
            if (!empty($accessers))
               $access = $this->inList($accessers, $this->user);
            
            if ($access == UNDEFINED)
            {
               $accessers = $this->rights->getValues($path);
               if (!empty($accessers))
                  $access = $this->inList($accessers, $this->user);
            }
             
            // If we've not got a match, remove the sub directory and start again
            if ($access == UNDEFINED)
            {
               if ($path == "/") break;
               $path = substr($path, 0, strrpos(substr($path, 0, -1), "/") + 1);
            }
                                          
         } while ($access == UNDEFINED && $path != "");
      }
      
      return $access == ALLOW;
   }

   // }}}
   
   // {{{ hasUnrestrictedReadAccess
   //
   // Returns true if the user has read access to the given path and too
   // all subfolders
   
   function hasUnrestrictedReadAccess($repos, $path)
   {
      // First make sure that we have full read access at this level
      
      if (!$this->hasReadAccess($repos, $path, false))
         return false;
         
      // Now check to see if there is a sub folder that's protected
      $repos = strtolower($repos); // .ini parser converts groups to lower-case
      $path = strtolower($path);
      
      $sections = $this->rights->getSections();
      
      foreach($sections As $section => $accessers)
      {
         $qualified = $repos.":".$path;
         $len = strlen($qualified);
         $access = UNDEFINED;
         
         if ($len <= strlen($section) && strncmp($section, $qualified, $len) == 0)
         {
            $access = $this->inList($accessers, $this->user);
         }

         if ($access != DENY)
         {
            $len = strlen($path);
            if ($len <= strlen($section) && strncmp($section, $path, $len) == 0)
            {
               $access = $this->inList($accessers, $this->user);
            }
         }
         
         if ($access == DENY)
            return false;
      }
      
      return true;
   }

   // }}}

}
?>
